import type { Metadata } from 'next'; import Link from 'next/link'; import { notFound } from 'next/navigation'; import { and, eq } from '@/lib/db'; import { db, collections, collectionItems } from '@/lib/db'; import { requireUser } from '@/lib/auth/session'; import { getAssetBrief, listCollections, loadItems } from '@/lib/account/queries'; import { valueItem } from '@/lib/account/portfolio'; import { getDisplay } from '@/lib/account/display'; import { deleteItemAction, moveItemAction, removeItemPhotoAction } from '@/lib/account/actions'; import { PageHeader, btnDanger, btnSecondary } from '@/components/account/page-header'; import { ValueSourceBadge } from '@/components/account/portfolio-widgets'; import { Card, CardHeader, Delta, Stat } from '@/components/ui/primitives'; import { confidenceLabel, fmtPct } from '@/lib/format'; import { EditItemForm } from '../../add-item-form'; import { PhotoUploadForm } from './photo-form'; export const metadata: Metadata = { title: 'Item', robots: { index: false } }; export default async function ItemPage({ params }: { params: Promise<{ id: string; itemId: string }> }) { const { id, itemId } = await params; const u = await requireUser(`/collections/${id}/items/${itemId}`); const own = await db().select({ id: collectionItems.id }).from(collectionItems).innerJoin(collections, eq(collections.id, collectionItems.collectionId)).where(and(eq(collectionItems.id, itemId), eq(collections.id, id), eq(collections.userId, u.id))).limit(1); if (!own[0]) notFound(); const loaded = (await loadItems([id])).find((i) => i.id === itemId); if (!loaded) notFound(); const v = valueItem(loaded); const brief = await getAssetBrief(loaded.assetId); const d = await getDisplay(); const others = (await listCollections(u.id)).filter((c) => c.collection.id !== id); return ( <> {loaded.categorySlug.replace(/_/g, ' ')} · {' '} Open asset page → } actions={ <> {others.length ? (
) : null}
} />
} sub={v.gainUsd === null ? '—' : d.money(v.gainUsd)} />
{brief?.stats ? (
Asset RIV
{brief.stats.rivUsd ? `${d.money(brief.stats.rivUsd)} (${brief.stats.rivSampleSize} sales)` : '—'}
Latest sale
{brief.stats.latestSaleUsd ? d.money(brief.stats.latestSaleUsd) : '—'}
30d / 1y
{fmtPct(brief.stats.change30d)} / {fmtPct(brief.stats.change1y)}
Liquidity · rarity
{brief.stats.liquidityScore ?? '—'} · {brief.stats.rarityScore ?? '—'}
Active listings
{brief.stats.activeListings}{brief.stats.minAskUsd ? ` · min ask ${d.money(brief.stats.minAskUsd)}` : ''}
Sales 30d
{brief.stats.sales30d}
) : null}
{loaded.photos.length ? (
    {loaded.photos.map((p) => (
  • {/* eslint-disable-next-line @next/next/no-img-element */}
  • ))}
) : (

No photos yet. Photos stay private unless the collection is public.

)}
); }